//
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import Foundation
import LibSignalClient
import XCTest

@testable import SignalServiceKit

private extension Aci {
    static let aci1 = Aci.randomForTesting()
    static let aci2 = Aci.randomForTesting()
    static let aci3 = Aci.randomForTesting()
}

class GroupModelsTest: SSKBaseTest {

    func testGroupMembershipChangingFullMembers() {
        var builder1 = GroupMembership.Builder()
        builder1.addFullMember(.aci1, role: .normal)
        let membership1 = builder1.build()

        var builder2 = GroupMembership.Builder()
        builder2.addFullMember(.aci1, role: .administrator)
        let membership2 = builder2.build()

        var builder3 = GroupMembership.Builder()
        builder3.addFullMember(.aci1, role: .normal)
        builder3.addFullMember(.aci2, role: .normal)
        let membership3 = builder3.build()

        var builder4 = GroupMembership.Builder()
        builder4.addFullMember(.aci1, role: .normal)
        let membership4 = builder4.build()

        XCTAssertEqual(membership1, membership4)

        XCTAssertNotEqual(membership1, membership2)

        XCTAssertNotEqual(membership1, membership3)

        XCTAssertNotEqual(membership2, membership3)
    }

    func testGroupMembershipChangingDidJoinFromInviteLink() {
        var builder1 = GroupMembership.Builder()
        builder1.addFullMember(.aci1, role: .normal, didJoinFromInviteLink: true)
        let membership1 = builder1.build()

        var builder2 = GroupMembership.Builder()
        builder2.addFullMember(.aci1, role: .normal, didJoinFromInviteLink: false)
        let membership2 = builder2.build()

        XCTAssertEqual(membership1, membership2)
    }

    func testGroupMembershipChangingRequestingMembers() {
        var builder1 = GroupMembership.Builder()
        builder1.addFullMember(.aci1, role: .normal)
        let membership1 = builder1.build()

        var builder2 = GroupMembership.Builder()
        builder2.addFullMember(.aci1, role: .normal)
        builder2.addRequestingMember(Aci.aci2)
        let membership2 = builder2.build()

        var builder3 = GroupMembership.Builder()
        builder3.addFullMember(.aci1, role: .normal)
        builder3.addRequestingMember(Aci.aci3)
        let membership3 = builder3.build()

        var builder4 = GroupMembership.Builder()
        builder4.addFullMember(.aci1, role: .normal)
        builder4.addRequestingMember(Aci.aci2)
        let membership4 = builder4.build()

        XCTAssertFalse(membership1 == membership2)

        XCTAssertFalse(membership1 == membership3)

        XCTAssertFalse(membership2 == membership3)

        XCTAssertTrue(membership2 == membership4)
    }

    func testGroupMembershipChangingBannedMembers() {
        var builder1 = GroupMembership.Builder()
        builder1.addFullMember(.aci1, role: .normal)
        let membership1 = builder1.build()

        var builder2 = membership1.asBuilder
        builder2.addFullMember(.aci1, role: .normal)
        builder2.addBannedMember(.aci2, bannedAtTimestamp: 3)
        let membership2 = builder2.build()

        var builder3 = GroupMembership.Builder()
        builder3.addFullMember(.aci1, role: .normal)
        builder3.addBannedMember(.aci2, bannedAtTimestamp: 12)
        let membership3 = builder3.build()

        var builder4 = GroupMembership.Builder()
        builder4.addFullMember(.aci1, role: .normal)
        builder4.addBannedMember(.aci2, bannedAtTimestamp: 3)
        builder4.addBannedMember(.aci3, bannedAtTimestamp: 4)
        let membership4 = builder4.build()

        var builder5 = GroupMembership.Builder()
        builder5.addFullMember(.aci1, role: .normal)
        builder5.addBannedMember(.aci2, bannedAtTimestamp: 3)
        builder5.addBannedMember(.aci3, bannedAtTimestamp: 4)
        let membership5 = builder5.build()

        XCTAssertNotEqual(membership1, membership2)

        XCTAssertNotEqual(membership2, membership3)

        XCTAssertNotEqual(membership3, membership4)

        XCTAssertEqual(membership4, membership5)
    }

    func testTSGroupModelBackwardsCompatibleDeserialization() throws {
        let groupIdLength = 16 // Taken from kGroupIdLength at the time of archiving.
        let expectedGroupId = Data(repeating: 8, count: groupIdLength)
        // hexstring generated by archiving:
        // let model = TSGroupModel(
        //     groupId: expectedGroupId,
        //     name: "name",
        //     avatarData: nil,
        //     members: [],
        //     addedBy: nil
        // )
        // try NSKeyedArchiver.archivedData(withRootObject: model, requiringSecureCoding: false).hexadecimalString
        let hexData = "62706c6973743030d4010203040506070a582476657273696f6e592461726368697665725424746f7058246f626a6563747312000186a05f100f4e534b657965644172636869766572d1080954726f6f748001a90b0c191a1b1c1d212755246e756c6cd60d0e0f1011121314151617185767726f757049645624636c6173735967726f75704e616d655c67726f75704d656d626572735f100f4d544c4d6f64656c56657273696f6e5f101767726f75704d6f64656c536368656d6156657273696f6e80058008800480068002800310001002546e616d654f101008080808080808080808080808080808d21e0e1f205a4e532e6f626a65637473a08007d2222324255a24636c6173736e616d655824636c6173736573574e534172726179a22426584e534f626a656374d2222328295c545347726f75704d6f64656ca32a2b265c545347726f75704d6f64656c584d544c4d6f64656c00080011001a00240029003200370049004c00510053005d006300700078007f0089009600a800c200c400c600c800ca00cc00ce00d000d200d700ea00ef00fa00fb00fd0102010d0116011e0121012a012f013c0140014d0000000000000201000000000000002c00000000000000000000000000000156"

        let encodedData = Data.data(fromHex: hexData)!

        let groupModel = try XCTUnwrap(NSKeyedUnarchiver.unarchivedObject(ofClass: TSGroupModel.self, from: encodedData, requiringSecureCoding: false))
        XCTAssertEqual(groupModel.groupId, expectedGroupId)
    }

    func testTSGroupModelV2BackwardsCompatibleDeserialization() throws {
        /// The value of ``kGroupIdLengthV2`` at the time of serialization.
        let groupIdLength = 32

        /// Generates a hex string of a serialized ``TSGroupModel`` with the
        /// given parameters, and the rest hardcoded.
        ///
        /// Use this to generate new models for back-compat deserialization
        /// testing as necessary.
        func generateHexString(
            groupIdRepeats: UInt8,
            isJoinRequestPlaceholder: Bool
        ) -> String {
            let model = TSGroupModelV2(
                groupId: Data(repeating: groupIdRepeats, count: groupIdLength),
                name: "name",
                descriptionText: "description",
                avatarDataState: .missing,
                groupMembership: .empty,
                groupAccess: .allAccess,
                revision: 1,
                secretParamsData: Data(repeating: 8, count: 10),
                avatarUrlPath: nil,
                inviteLinkPassword: nil,
                isAnnouncementsOnly: false,
                isJoinRequestPlaceholder: isJoinRequestPlaceholder,
                wasJustMigrated: false,
                didJustAddSelfViaGroupLink: false,
                addedByAddress: nil
            )

            return try! NSKeyedArchiver.archivedData(
                withRootObject: model,
                requiringSecureCoding: false
            ).hexadecimalString
        }

        let testCases: [(encodedData: Data, expectedGroupId: Data, isJoinRequestPlaceholder: Bool)] = [
            (
                encodedData: .data(
                    fromHex: "62706c6973743030d4010203040506070a582476657273696f6e592461726368697665725424746f7058246f626a6563747312000186a05f100f4e534b657965644172636869766572d1080954726f6f748001af10140b0c2d2e2f30313a3b3c44454d4e54585c5f626355246e756c6cdf10100d0e0f101112131415161718191a1b1c1d1e1f20211d2324251d2728292a1d1d5f10197761734a7573744372656174656442794c6f63616c557365725f1010736563726574506172616d73446174615f100f4d544c4d6f64656c56657273696f6e566163636573735f100f6465736372697074696f6e546578745f10126973506c616365686f6c6465724d6f64656c587265766973696f6e5a6d656d626572736869705624636c6173735f101a6469644a75737441646453656c6656696147726f75704c696e6b5e64726f707065644d656d626572735f101767726f75704d6f64656c536368656d6156657273696f6e5967726f75704e616d655767726f757049645f10136973416e6e6f756e63656d656e74734f6e6c795f100f7761734a7573744d696772617465648005800380028006800a80058008800b8013800580108007801280048005800510004a080808080808080808084f1020080808080808080808080808080808080808080808080808080808080808080808d515320f333435231f28285f101161646446726f6d496e766974654c696e6b576d656d626572735a617474726962757465738009800880028007800710021001d23d3e3f405a24636c6173736e616d655824636c61737365735f101c5369676e616c536572766963654b69742e47726f7570416363657373a34142435f101c5369676e616c536572766963654b69742e47726f7570416363657373584d544c4d6f64656c584e534f626a6563745b6465736372697074696f6ed415464748494a4a4c5f1010696e76616c6964496e766974654d61705d62616e6e65644d656d626572735c6d656d626572537461746573800f800d800d800c425b5dd34f5015515253574e532e6b6579735a4e532e6f626a65637473a0a0800ed23d3e55565c4e5344696374696f6e617279a257435c4e5344696374696f6e617279d23d3e595a5f10205369676e616c536572766963654b69742e47726f75704d656d62657273686970a35b42435f10205369676e616c536572766963654b69742e47726f75704d656d62657273686970d250155d5ea08011d23d3e6061574e534172726179a26043546e616d65d23d3e64655f101f5369676e616c536572766963654b69742e545347726f75704d6f64656c5632a4666742435f101f5369676e616c536572766963654b69742e545347726f75704d6f64656c56325c545347726f75704d6f64656c00080011001a00240029003200370049004c00510053006a0070009300af00c200d400db00ed0102010b0116011d013a01490163016d0175018b019d019f01a101a301a501a701a901ab01ad01af01b101b301b501b701b901bb01bd01bf01ca01ed01ee01f9020d021502200222022402260228022a022c022e0233023e02470266026a02890292029b02a702b002c302d102de02e002e202e402e602e902f002f80303030403050307030c0319031c0329032e035103550378037d037e03800385038d03900395039a03bc03c103e300000000000002010000000000000068000000000000000000000000000003f0"
                )!,
                expectedGroupId: Data(repeating: 8, count: groupIdLength),
                isJoinRequestPlaceholder: false
            ),
            (
                encodedData: .data(
                    fromHex: "62706c6973743030d4010203040506070a582476657273696f6e592461726368697665725424746f7058246f626a6563747312000186a05f100f4e534b657965644172636869766572d1080954726f6f748001af10140b0c2b2c2d2e37383941424a4b5155595c5f606155246e756c6cdf100f0d0e0f101112131415161718191a1b1c1d1e1f20212223202526272820205f1010736563726574506172616d73446174615f100f4d544c4d6f64656c56657273696f6e566163636573735f100f6465736372697074696f6e546578745f10126973506c616365686f6c6465724d6f64656c587265766973696f6e5a6d656d626572736869705624636c6173735f101a6469644a75737441646453656c6656696147726f75704c696e6b5e64726f707065644d656d626572735f101767726f75704d6f64656c536368656d6156657273696f6e5967726f75704e616d655767726f757049645f10136973416e6e6f756e63656d656e74734f6e6c795f100f7761734a7573744d69677261746564800380028005800980048007800a80138004800f8006801180128004800410004a0808080808080808080808d5142f0e303132211d26265f101161646446726f6d496e766974654c696e6b576d656d626572735a617474726962757465738008800780028006800610021001d23a3b3c3d5a24636c6173736e616d655824636c61737365735f101c5369676e616c536572766963654b69742e47726f7570416363657373a33e3f405f101c5369676e616c536572766963654b69742e47726f7570416363657373584d544c4d6f64656c584e534f626a6563745b6465736372697074696f6ed414434445464747495f1010696e76616c6964496e766974654d61705d62616e6e65644d656d626572735c6d656d626572537461746573800e800c800c800b425b5dd34c4d144e4f50574e532e6b6579735a4e532e6f626a65637473a0a0800dd23a3b52535c4e5344696374696f6e617279a254405c4e5344696374696f6e617279d23a3b56575f10205369676e616c536572766963654b69742e47726f75704d656d62657273686970a3583f405f10205369676e616c536572766963654b69742e47726f75704d656d62657273686970d24d145a5ba08010d23a3b5d5e574e534172726179a25d40546e616d654f10200808080808080808080808080808080808080808080808080808080808080808d23a3b62635f101f5369676e616c536572766963654b69742e545347726f75704d6f64656c5632a464653f405f101f5369676e616c536572766963654b69742e545347726f75704d6f64656c56325c545347726f75704d6f64656c00080011001a00240029003200370049004c00510053006a0070009100a400b600bd00cf00e400ed00f800ff011c012b0145014f0157016d017f01810183018501870189018b018d018f01910193019501970199019b019d019f01aa01ab01b601ca01d201dd01df01e101e301e501e701e901eb01f001fb0204022302270246024f02580264026d0280028e029b029d029f02a102a302a602ad02b502c002c102c202c402c902d602d902e602eb030e03120335033a033b033d0342034a034d03520375037a039c03a103c300000000000002010000000000000066000000000000000000000000000003d0"
                )!,
                expectedGroupId: Data(repeating: 8, count: groupIdLength),
                isJoinRequestPlaceholder: false
            ),
            (
                encodedData: .data(
                    fromHex: "62706c6973743030d4010203040506070a582476657273696f6e592461726368697665725424746f7058246f626a6563747312000186a05f100f4e534b657965644172636869766572d1080954726f6f748001af10150b0c2b2c2d2e3738394142434b4c52565a5d60616255246e756c6cdf100f0d0e0f101112131415161718191a1b1c1d1e1f20212223242526272824245f1010736563726574506172616d73446174615f100f4d544c4d6f64656c56657273696f6e566163636573735f100f6465736372697074696f6e546578745f10126973506c616365686f6c6465724d6f64656c587265766973696f6e5a6d656d626572736869705624636c6173735f101a6469644a75737441646453656c6656696147726f75704c696e6b5e64726f707065644d656d626572735f101767726f75704d6f64656c536368656d6156657273696f6e5967726f75704e616d655767726f757049645f10136973416e6e6f756e63656d656e74734f6e6c795f100f7761734a7573744d696772617465648003800280058009800a8007800b8014800480108006801280138004800410004a0808080808080808080808d5142f0e303132211d26265f101161646446726f6d496e766974654c696e6b576d656d626572735a617474726962757465738008800780028006800610021001d23a3b3c3d5a24636c6173736e616d655824636c61737365735f101c5369676e616c536572766963654b69742e47726f7570416363657373a33e3f405f101c5369676e616c536572766963654b69742e47726f7570416363657373584d544c4d6f64656c584e534f626a6563745b6465736372697074696f6e09d4144445464748484a5f1010696e76616c6964496e766974654d61705d62616e6e65644d656d626572735c6d656d626572537461746573800f800d800d800c425b5dd34d4e144f5051574e532e6b6579735a4e532e6f626a65637473a0a0800ed23a3b53545c4e5344696374696f6e617279a255405c4e5344696374696f6e617279d23a3b57585f10205369676e616c536572766963654b69742e47726f75704d656d62657273686970a3593f405f10205369676e616c536572766963654b69742e47726f75704d656d62657273686970d24e145b5ca08011d23a3b5e5f574e534172726179a25e40546e616d654f10200707070707070707070707070707070707070707070707070707070707070707d23a3b63645f101f5369676e616c536572766963654b69742e545347726f75704d6f64656c5632a465663f405f101f5369676e616c536572766963654b69742e545347726f75704d6f64656c56325c545347726f75704d6f64656c00080011001a00240029003200370049004c00510053006b0071009200a500b700be00d000e500ee00f90100011d012c014601500158016e01800182018401860188018a018c018e01900192019401960198019a019c019e01a001ab01ac01b701cb01d301de01e001e201e401e601e801ea01ec01f101fc02050224022802470250025902650266026f02820290029d029f02a102a302a502a802af02b702c202c302c402c602cb02d802db02e802ed031003140337033c033d033f0344034c034f03540377037c039e03a303c500000000000002010000000000000067000000000000000000000000000003d2"
                )!,
                expectedGroupId: Data(repeating: 7, count: groupIdLength),
                isJoinRequestPlaceholder: true
            )
        ]

        for (encodedData, expectedGroupId, isJoinRequestPlaceholder) in testCases {
            let groupModel = try XCTUnwrap(NSKeyedUnarchiver.unarchivedObject(
                ofClass: TSGroupModelV2.self,
                from: encodedData,
                requiringSecureCoding: false
            ))

            XCTAssertEqual(groupModel.groupId, expectedGroupId)
            XCTAssertEqual(groupModel.groupName, "name")
            XCTAssertEqual(groupModel.descriptionText, "description")
            XCTAssertEqual(groupModel.groupMembership, .empty)
            XCTAssertEqual(groupModel.access, .allAccess)
            XCTAssertEqual(groupModel.revision, 1)
            XCTAssertEqual(groupModel.secretParamsData, Data(repeating: 8, count: 10))
            XCTAssertNil(groupModel.avatarUrlPath)
            switch groupModel.avatarDataState {
            case .missing: break
            case .available, .failedToFetchFromCDN, .lowTrustDownloadWasBlocked: XCTFail("Unexpected avatar data state")
            }
            XCTAssertNil(groupModel.inviteLinkPassword)
            XCTAssertFalse(groupModel.isAnnouncementsOnly)
            XCTAssertEqual(groupModel.isJoinRequestPlaceholder, isJoinRequestPlaceholder)
            XCTAssertFalse(groupModel.wasJustMigrated)
            XCTAssertFalse(groupModel.didJustAddSelfViaGroupLink)
            XCTAssertNil(groupModel.addedByAddress)
        }
    }
}
